/*
        Assignment week 3 cg 2010-2011 
        OpenGL: Phong shading with GLSL (3)
        by      Zhe Sun
                Gerard Knap s1222376

        Use r-mouse down + up & down movement of the mouse to zoom in and out.
        Use l-mouse down to rotate arround the cube
        Use wasd on the keyboard for panning and zooming

        most used sources used for this program in random order:
        - http://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/lighting.php
        - http://www.hlc-games.de/forum/viewtopic.php?f=10&t=56
        - http://www.cs.wpi.edu/~emmanuel/courses/cs543/slides/cliff_iintro_glsl_final.pdf
        - http://www.ozone3d.net/tutorials/glsl_lighting_phong.php
        - http://nehe.gamedev.net/data/articles/article.asp?article=21
        - the course book
*/

/*
* An OpenGL template/framework file for the Computer Graphics course
* at the University of Groningen.
*/

// If windows is used, windows.h should be included (before gl.h and glu.h)
#if defined(_WIN32)
#include <windows.h>
#endif

// If you use GLUT you do not need to include gl.h and glu.h
// as glut.h guarantees that gl.h and glu.h are properly 
// included.
// So if you do not wish to use  glut.h, uncomment the following lines.
//#include <GL/gl.h>
//#include <GL/glu.h>

#if defined(NEED_GLEW)
#include "glew.h"
#endif

// Apparently Mac OS X puts the GLUT headers somewhere different.
// For windows we use freeglut.
#if defined(__APPLE__)&& defined(__MACH__)
#include <GLUT/glut.h>
#elif defined(_WIN32)
#include <GL/freeglut.h>
#else
#include <GL/glut.h>
#endif

#include <stdlib.h>
#include <stdio.h>

#include <math.h>
#include "glslshaders.h"

#ifndef M_PI
#define M_PI           3.14159265358979323846
#endif
  

#define SPHERE_N (20)

void idle();

//variables used to store mouse-coordinates
GLfloat startDist=0, panning=0 ;
int prevX=0, prevY=0, startX=0, startY=0;
 
//variables used to change the viewpoint
GLfloat alt = 0, azim = 0, height = 0.5, distance = 0 ;
int doZoom , doRotate ;
 
#if !defined true
        #define         true   (0==0)
        #define         false   (!true)
#endif


void setGlMaterial(GLfloat r, GLfloat g, GLfloat b, GLfloat ka, GLfloat kd, GLfloat ks, GLfloat n)
{
    GLfloat ambient[] = {ka*r,ka*g,ka*b,1.0};
    GLfloat diffuse[] = {kd*r,kd*g,kd*b,1.0};
    GLfloat specular[] = {ks,ks,ks,1.0};
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, n);
}


void placeSpehers() {   
    setGlMaterial(0.0f,0.0f,1.0f,0.2,0.7,0.5,64);
    glPushMatrix();
    glTranslated(90,320,100);
    glutSolidSphere(50,SPHERE_N,SPHERE_N);
    glPopMatrix();

    setGlMaterial(0.0f,1.0f,0.0f,0.2,0.3,0.5,8);
    glPushMatrix();
    glTranslated(210,270,300);
    glutSolidSphere(50,SPHERE_N,SPHERE_N);
    glPopMatrix();

    setGlMaterial(1.0f,0.0f,0.0f,0.2,0.7,0.8,32);
    glPushMatrix();
    glTranslated(290,170,150);
    glutSolidSphere(50,SPHERE_N,SPHERE_N);
    glPopMatrix();

    setGlMaterial(1.0f,0.8f,0.0f,0.2,0.8,0.0,1);
    glPushMatrix();
    glTranslated(140,220,400);
    glutSolidSphere(50,SPHERE_N,SPHERE_N);
    glPopMatrix();

    setGlMaterial(1.0f,0.5f,0.0f,0.2,0.8,0.5,32);
    glPushMatrix();
    glTranslated(110,130,200);
    glutSolidSphere(50,SPHERE_N,SPHERE_N);
    glPopMatrix();
}

void placeLights() {
   GLfloat onesQuad[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat light_position[] = {-200.0, 600.0, 1500.0, 0.0} ; 

   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_SMOOTH);

   glMaterialfv(GL_FRONT, GL_AMBIENT, onesQuad);
   glMaterialfv(GL_FRONT, GL_SPECULAR, onesQuad);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, onesQuad);
   glLightfv(GL_LIGHT0, GL_POSITION, light_position);

}


void mouse(int btn, int state, int x, int y)
{
        startX = x;
        startY = y;
        prevX = x;
        prevY = y;

        startDist = distance;

        if(btn==GLUT_LEFT_BUTTON)  { // the l-mouse-button is voor rotating arround the cube
                doRotate = state==GLUT_DOWN;
                doZoom = false;
        }
        if(btn==GLUT_RIGHT_BUTTON)   { // the r-mouse-button is voor zooming 
                doRotate = false;
                doZoom = state==GLUT_DOWN;
        }
        if(btn==GLUT_MIDDLE_BUTTON && state==GLUT_DOWN)   { // close on middle click
                exit( 0 );
        }

        //printf("x:%d, y%d",x, y); 
}

// if the user moves the mouse... calculate how much is moved and depening if the user is zooming or rotating
//change the parameters accordingly. This function also updates prevX and prevY.
void motion(int x, int y) 
{
        int dx, dy;

        dx = startX - x;
        dy = startY - y;
        
        if(doZoom) {
                distance = (2 * dy) + startDist;
        } else if(doRotate) {
                azim -= 0.5 * (prevX - x);
                alt -= 0.2 * (prevY - y);
        }
 
        prevX = x;
        prevY = y;
        //printf("x: %d, y:%d, dx: %d, dy:%d\n" , x, y,dx,dy);
}

//if parameters like azim or alt are changed, this function wil redraw the cube with the specified params
// use eyeX eyeY to implement the depth of field
void setViewPoint(GLdouble eyeX, GLdouble eyeY)
{
  glLoadIdentity();
  gluLookAt(eyeX+200.0, eyeY+200.0, 1000.0+distance,   200.0+panning,200.0,0.0,   0.0,1.0,0.0);
  glRotatef(alt,1,0,0);
  glRotatef(azim,0,1,0);
}

// focus is distance from eye to plane in focus. 
// Use glFrustum to implement depth of the field
void setViewPoint_withFocus(GLdouble eyedx, GLdouble eyedy, GLdouble focus)
{
   GLdouble fovy, fovy2;
   GLdouble left,right,bottom,top;
   GLdouble dx, dy;
   GLint viewport[4];

   // the same configuration as reshape
   GLdouble nearView = 500;
   GLdouble farView = 1000;
   
   // get h and w 
   glGetIntegerv (GL_VIEWPORT, viewport);
   
   // get t/b/l/r from fovy, near and far
   fovy = atan2(viewport[3]/2.0,500);
   
   top = nearView / (cos(fovy) / sin(fovy));
   bottom = -top;

   right = top * ((GLdouble)viewport[2]/(GLdouble)viewport[3]);
   left = -right;

   // get the offset of the view
   dx =  eyedx*nearView/focus;
   dy =  eyedy*nearView/focus;

   // adjust the view
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glFrustum (left + dx, right + dx, bottom + dy, top + dy, 500, 1000);

   // adjust the object
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef (-eyedx, -eyedy, 0.0);

   // set eye position to implement zoom in/out and left/right shift
    glLoadIdentity();
    gluLookAt(200.0, 200.0, 1000.0+distance,   200.0+panning,200.0,0.0,   0.0,1.0,0.0);

    // implement mouse rotation by glRotate
    glRotatef(alt,1,0,0);
    glRotatef(azim,0,1,0);


}



void fermat(int n, int c, GLdouble *eyeX, GLdouble *eyeY){
  GLdouble r, theta;

  if (!eyeX || !eyeY)
    return;
    
  /*
  theta = (n/2) * 137.508 * M_PI / 180.0;
  if ( n % 2 == 0) {
    r = c * sqrt(n);
  } else {
    r = - c * sqrt(n);
  }
  */
  
  // only positive r is enough
  r = c * sqrt(n);
  theta = n * 137.508 * M_PI / 180.0;
  
  *eyeX = r * cos(theta);
  *eyeY = r * sin(theta);
  
  return;
}

#define APERTURE_SAMPLES 8

struct {
  GLdouble x;
  GLdouble y;
}jitter8[] = {{0.4166666666, 0.625}, {0.9166666666, 0.875}, {0.25, 0.375},
{0.4166666666, 0.125}, {0.75, 0.125}, {0.0833333333, 0.125}, {0.75, 0.625},
{0.25, 0.875}, {0.5833333333, 0.375}, {0.9166666666, 0.375},
{0.0833333333, 0.625}, {0.583333333, 0.875}};

// Depth of field
void display(void)
{
    int i;
    GLdouble eyeX = 0.0, eyeY = 0.0;
    GLdouble focus = 200;
    
    glClear(GL_ACCUM_BUFFER_BIT);
    for (i=0; i<APERTURE_SAMPLES; i++) {
    /* Clear all pixels */
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    
#if 0
  // all view is blur, not really world
  // use scale constance as 1
        fermat(i, 1, &eyeX, &eyeY);
        setViewPoint(eyeX, eyeY);
#else
  // Use glFrustum to implement to focus and depth of field
  eyeX = jitter8[i].x;
  eyeY = jitter8[i].y;
  setViewPoint_withFocus(eyeX, eyeY, focus);
#endif

        /* Set up other things you may need */
        placeSpehers();
        placeLights();

      
        glAccum (GL_ACCUM, 1.0/APERTURE_SAMPLES);
    }
    glAccum (GL_RETURN, 1);
    //glFlush();
    glutSwapBuffers();
}

void keyboard(unsigned char key, int x, int y)
{
    switch (key) {
        case 'q':
        case 'Q':
        case 27: // ESC key
            printf("Exiting...\n");
            exit(0);
            break;
                case 'a':
                        panning += 20.0f;
                        break;
                case 'd' :
                        panning -= 20.0f;
                        break;
                case 's':
                        distance += 20.0f;
                        break;
                case 'w':
                        distance -= 20.0f;
                        break;
    }
}

void reshape(int w, int h)
{
    glViewport(0,0, (GLsizei) w, (GLsizei) h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(2.0*atan2(h/2.0,1000.0)*180.0/M_PI,(GLdouble)w/(GLdouble)h,500,1000);
    glMatrixMode(GL_MODELVIEW);    
}

void idle()
{ 
        display();
}



int main(int argc, char** argv)
{
#if defined(NEED_GLEW)
    GLenum err;
#endif


    glutInit(&argc,argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_ACCUM);
        glutInitWindowSize(400,400);
    glutInitWindowPosition(220,100);
    glutCreateWindow("Computer Graphics - OpenGL framework");
        
#if defined(NEED_GLEW)
    /* Init GLEW if needed */
    err = glewInit();
    if (GLEW_OK != err) {
        /* Problem: glewInit failed, something is seriously wrong. */
        fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
        exit(1);
    }
    fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));
#endif

    /* Select clearing (background) color */
    glClearColor(0.0,0.0,0.0,0.0);
        glShadeModel(GL_SMOOTH);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
    glEnable(GL_DEPTH_TEST);
        initGLSLProgram("vertexshader.glsl","fragmentshader.glsl");

    /* Register GLUT callback functions */
    glutDisplayFunc(display);
    glutKeyboardFunc(keyboard);
    glutReshapeFunc(reshape);

        glutIdleFunc(idle);
        glutMouseFunc(mouse);
        glutMotionFunc(motion);
    glutMainLoop();

    return 0;
}
